home *** CD-ROM | disk | FTP | other *** search
/ MacFormat 1999 April / macformat-075.iso / Shareware Plus / Applications / Alpha / Tcl / SystemCode / CorePackages / fileManipulation.tcl < prev    next >
Encoding:
Text File  |  1999-01-31  |  25.6 KB  |  867 lines  |  [TEXT/ALFA]

  1. ## -*-Tcl-*-
  2.  # ###################################################################
  3.  #  Vince's Additions --- an extension package for Alpha
  4.  # 
  5.  #  FILE: "fileManipulation.tcl"
  6.  #                                    created: 24/2/98 {1:57:08 pm} 
  7.  #                                last update: 31/1/1999 {11:26:57 pm} 
  8.  #  Author: Vince Darley
  9.  #  E-mail: <darley@fas.harvard.edu>
  10.  #    mail: Division of Engineering and Applied Sciences, Harvard University
  11.  #          Oxford Street, Cambridge MA 02138, USA
  12.  #     www: <http://www.fas.harvard.edu/~darley/>
  13.  #  
  14.  # Mostly Copyright (c) 1998  Vince Darley
  15.  # 
  16.  # See the file "license.terms" for information on usage and redistribution
  17.  # of this file, and for a DISCLAIMER OF ALL WARRANTIES.
  18.  # ###################################################################
  19.  ##
  20.  
  21. # extension declaration
  22. alpha::extension fileManipulation 1.02 {
  23. } maintainer {
  24.     {Vince Darley} <darley@fas.harvard.edu> <http://www.fas.harvard.edu/~darley/>
  25.  
  26. namespace eval file {}
  27.  
  28. proc file::showInFinder {{f {}}} {
  29.     if {$f == ""} {set f [win::Current]}
  30.     if {![file exists "$f"]} {
  31.     message "ERROR: FILE NOT FOUND: \"$f\""
  32.     return
  33.     }
  34.     switchTo Finder
  35.     AEBuild Finder misc mvis "----" [makeAlis $f]
  36. }
  37.  
  38. proc file::tryToOpen {{fname ""}} {
  39.     if {$fname == ""} {set fname [getSelect]}
  40.     set f [file join [file dirname [win::Current]] $fname]
  41.     if {[file exists $f]} {
  42.     file::openQuietly $f
  43.     } else {
  44.     alertnote "Sorry, I couldn't find that file.  You could install\
  45.       Vince's Additions which includes better include-path handling."
  46.     }
  47. }
  48.  
  49. proc file::ensureDirExists {dir} {
  50.     if {![file exists $dir]} {
  51.     if {$dir == ""} {
  52.         error "Can't create the folder because the disk doesn't exist."
  53.     }
  54.     file::ensureDirExists [file dirname $dir]
  55.     file mkdir $dir
  56.     return 1
  57.     }
  58.     return 0
  59. }
  60.  
  61. proc file::openAny {file} {
  62.     getFileInfo $file a
  63.     if {![info exists a(type)] || ($a(type) == "TEXT")} {
  64.     edit $file
  65.     return
  66.     } else {
  67.     sendOpenEvent -noreply Finder "${file}"
  68.     }
  69. }
  70.  
  71. proc file::renameTo {} {
  72.     set c [win::Current]
  73.     if {![file exists $c]} { alertnote "Not a file window!" ; return }
  74.     set new [prompt "New name for file:" [file tail $c]]
  75.     if {[file exists [set to [file join [file dirname $c] $new]]]} {
  76.     alertnote "Already exists!"
  77.     return
  78.     }
  79.     killWindow
  80.     file rename $c $to
  81.     edit $to
  82. }
  83.  
  84. proc file::standardFind {f} {
  85.     global HOME auto_path PREFS tclExtensionsFolder file::separator
  86.     set dirs $auto_path
  87.     lappend dirs [file join $HOME Tcl Completions] $PREFS \
  88.       [file join $HOME Help] [file join $HOME Tools]
  89.     if {[info exists tclExtensionsFolder]} { lappend dirs $tclExtensionsFolder }
  90.     foreach dir $dirs {
  91.     if {[file exists [file join ${dir} ${f}]]} {
  92.         return [file join ${dir} ${f}]
  93.     }
  94.     }
  95.     if {[regexp ${file::separator} $f]} {
  96.     foreach dir $dirs {
  97.         if {[file exists [file join [file dirname $dir] $f]]} {
  98.         return [file join [file dirname $dir] $f]
  99.         }
  100.     }
  101.     }
  102.     error "Not found"    
  103. }
  104.  
  105. ## 
  106.  # -------------------------------------------------------------------------
  107.  # 
  108.  # "file::hyperOpen" --
  109.  # 
  110.  #  Called by embedded hyperlinks; we look through an installation
  111.  #  directory (and subdirs) if it is known, then the prefs directory, 
  112.  #  then all of the auto_path.  If the file is of type TEXT, we open
  113.  #  it, else we ask the finder to open it.
  114.  # -------------------------------------------------------------------------
  115.  ##
  116. proc file::hyperOpen { name } {
  117.     global PREFS tclExtensionsFolder auto_path file::separator
  118.     set currD [list [file dirname [win::Current]]]
  119.     set dirs [glob -nocomplain "[file join $currD *]${file::separator}"]
  120.     foreach d $dirs {
  121.     lappend currD [string trimright $d ${file::separator}]
  122.     }
  123.     lappend currD $PREFS 
  124.     if {[info exists tclExtensionsFolder]} { lappend currD $tclExtensionsFolder }
  125.     foreach d [concat $currD $auto_path] {
  126.     if {[file exists [file join $d $name]]} {
  127.         file::openAny [file join $d $name]
  128.         return
  129.     }
  130.     }
  131.     beep
  132.     message "Sorry, couldn't find $name"
  133. }
  134. ## 
  135.  # -------------------------------------------------------------------------
  136.  # 
  137.  # "file::hyperHelpOpen" --
  138.  # 
  139.  #  Called by embedded hyperlinks; we look through an installation
  140.  #  directory (and subdirs) if it is known, then the prefs directory, 
  141.  #  then all of the auto_path.
  142.  # -------------------------------------------------------------------------
  143.  ##
  144. proc file::hyperHelpOpen { name } {
  145.     global HOME auto_path file::separator
  146.     set currD [list [file dirname [win::Current]]]
  147.     set dirs [glob -nocomplain "[file join $currD *]${file::separator}"]
  148.     foreach d $dirs {
  149.     lappend currD [string trimright $d ${file::separator}]
  150.     }
  151.     lappend currD $HOME:Help
  152.     foreach d [concat $currD $auto_path] {
  153.     set ns [glob -nocomplain [file join $d ${name}*]]
  154.     foreach n $ns {
  155.         if {[regexp -nocase "help" [file tail $n]]} {
  156.         edit $n
  157.         return
  158.         }
  159.     }
  160.     }
  161.     beep
  162.     message "Sorry, couldn't find a help file for $name"
  163. }
  164.  
  165. ## 
  166.  # -------------------------------------------------------------------------
  167.  # 
  168.  # "file::jumpToCode" --
  169.  # 
  170.  #  It creates a hyperlink to a specific string in a code file, without
  171.  #  requiring a mark to be defined there. It was handy for identifying places 
  172.  #  in other packages that potentially collide with my key-bindings.
  173.  #  
  174.  #  Author: Jon Guyer.
  175.  # -------------------------------------------------------------------------
  176.  ##
  177. proc file::jumpToCode {text file code} {
  178.     set hyper {edit -c }
  179.     append hyper $file
  180.     append hyper { ; set pos [search -f 1 -r 1 "}
  181.     append hyper $code
  182.     append hyper {"] ; select [lindex $pos 0] [lindex $pos 1]}
  183.     file::searchAndHyperise $text $hyper 0 3
  184. }
  185.  
  186.  
  187. proc file::sameModifiedDate {a b} {
  188.     getFileInfo $a infoa
  189.     getFileInfo $b infob
  190.     # bigger = newer
  191.     set ma $infoa(modified)
  192.     set mb $infob(modified)
  193.     return [expr {$ma == $mb ? 1 : 0}]
  194. }
  195.  
  196. proc file::secondIsOlder {a b} {
  197.     getFileInfo [stripNameCount $a] infoa
  198.     getFileInfo [stripNameCount $b] infob
  199.     # bigger = newer
  200.     set ma $infoa(modified)
  201.     set mb $infob(modified)
  202.     return [expr {$ma > $mb ? 1 : 0}]
  203. }
  204.  
  205. proc file::replaceSecondIfOlder {a b {complain 1} {backup ""}} {
  206.     if {![file exists $a]} { error "First does not exist!" }
  207.     if {[file exists $b]} {
  208.     if {[file::secondIsOlder $a $b]} {
  209.         file::remove [file dirname $b] [list [file tail $b]] $backup
  210.         file copy $a $b
  211.         install::log "Copied [file tail $a] to $b"
  212.         return 1
  213.     } elseif {[file::secondIsOlder $b $a]} {
  214.         install::log "The pre-existing [file tail $a] is newer than the one which was to be installed."
  215.     }
  216.     } elseif {$complain} { 
  217.     error "Second does not exist!"
  218.     } else {
  219.     file copy $a $b
  220.     install::log "Copied [file tail $a] to $b"
  221.     } 
  222.     return 0
  223. }
  224.  
  225. proc file::removeCheckingWins {f} {
  226.     install::log "Removed $f"
  227.     if {[set i [lsearch -regexp [winNames -f] "^[quote::Regfind $f]( <\d+>)?$"]] != -1} {
  228.     bringToFront [lindex [winNames -f] $i]
  229.     killWindow
  230.     file delete $f
  231.     return 1
  232.     }
  233.     file delete $f
  234.     return 0
  235. }
  236.  
  237. proc file::remove {to files {backup ""}} {
  238.     foreach f $files {
  239.     if {[file exists [file join $to $f]]} {
  240.         file::removeOne [file join $to $f] $backup
  241.     }
  242.     }
  243. }
  244.  
  245. proc file::removeOne {f {backup ""}} {
  246.     set ff [file tail $f]
  247.     message "Removing old '$ff'"
  248.     if {${backup} != ""} {
  249.     if {![file exists $backup]} { file mkdir $backup }
  250.     set i ""
  251.     while {[file exists [file join $backup $ff$i]]} {
  252.         if {$i == ""} { set i 0}
  253.         incr i
  254.     }
  255.     file copy $f [file join ${backup} $ff$i]
  256.     }
  257.     file::removeCheckingWins $f
  258. }
  259.  
  260. proc file::getSig {f} {
  261.     if {[catch {getFileInfo $f arr}]} { return "" }
  262.     return $arr(creator)
  263. }
  264.  
  265.  
  266. ## 
  267.  # ----------------------------------------------------------------------
  268.  #     
  269.  #    "file::searchAndHyperise"    --
  270.  #    
  271.  #     Scans through an entire file for a    particular string or
  272.  #     regexp, and attaches a    hyperlink of the specified form
  273.  #     (regsub'ed    if desired)    to the original    string.
  274.  #            
  275.  #    Side effects:
  276.  #     Many hyperlinks will be embedded in your file
  277.  #    
  278.  #    Arguments:
  279.  #     Look for 'text', replace with 'link', doing both with a regexp
  280.  #     if signified (regexp = 1), using colour 'col', and offsetting
  281.  #     the link start and end by 'startoff' and 'endoff' respectively.
  282.  #     This last bit is so you can search for a large pattern, but only
  283.  #     embed a link in a smaller part of it.
  284.  #     
  285.  #    Examples: 
  286.  #     see 'proc install::hyperiseUrls'
  287.  # ----------------------------------------------------------------------
  288.  ##
  289. proc file::searchAndHyperise { text link {regexp 0} {col 3} {startoff 0} {endoff 0}} {
  290.     set pos [minPos]
  291.     catch {
  292.     while 1 {
  293.         set inds [search -s -f 1 -r $regexp -- $text $pos]
  294.         set from [lindex $inds 0]
  295.         set to [lindex $inds 1]
  296.         set realfrom $from
  297.         set realto $to
  298.         set realfrom [pos::math $realfrom + $startoff]
  299.         set realto [pos::math $realto + $endoff]
  300.         text::color $realfrom $realto $col
  301.         if {$link != ""} {
  302.         if {$regexp} {
  303.             regsub $text [getText $from $to] "$link" llink
  304.         } else {
  305.             set llink $link
  306.         }
  307.         text::hyper $realfrom $realto $llink
  308.         }
  309.         set pos $to
  310.     }    
  311.     }
  312.     refresh
  313. }
  314. proc file::multiSearchAndHyperise {args} {
  315.     while 1 {
  316.     set text [lindex $args 0]
  317.     set link [lindex $args 1]
  318.     set args [lrange $args 2 end]
  319.     if {$text == ""} {return}
  320.     file::searchAndHyperise $text $link
  321.     }
  322. }
  323.  
  324. ## 
  325.  # -------------------------------------------------------------------------
  326.  # 
  327.  # "file::findAllInstances" --
  328.  # 
  329.  #  Returns all instances of a given pattern in a file.  This is a regexp
  330.  #  search, and the pattern must match all the way to the end of the 
  331.  #  file.  Here is an example usage:
  332.  #  
  333.  #      set pat2 {^.*\\(usepackage|RequirePackage)\{([^\}]+)\}(.*)$}
  334.  #      set subpkgs [file::findAllInstances $filename $pat2 1]
  335.  #  
  336.  #  Notice the pattern ends in '(.*)$', this is important.
  337.  #  Notice that since there is one extra '()' pair in the regexp,
  338.  #  we give '1' as the last argument.
  339.  #  
  340.  #  WARNING:  Calling this procedure incorrectly can easily result
  341.  #  in an infinite loop.  This will tend to crash Alpha and is hard
  342.  #  to debug using trace-dumps, because Alpha will tend to crash
  343.  #  whilst tracing too!  To debug, modify the 'while' loop so that it
  344.  #  also increments a counter, and stops after a few iterations.
  345.  # -------------------------------------------------------------------------
  346.  ##
  347. proc file::findAllInstances {filename searchString {extrabrackets 0}} {
  348.     # Get the text of the file to be searched:
  349.     if {[lsearch [winNames -f] $filename] >= 0} {
  350.     set fileText [getText -w $filename [minPos] [maxPos -w $filename]]
  351.     } elseif {[file exists $filename]} {
  352.     set fd [open $filename]
  353.     set fileText [read $fd]
  354.     close $fd
  355.     } else {
  356.     return ""
  357.     }
  358.     # Search the text for the search string:
  359.     while {[string length $fileText]} {
  360.     set dmy [lrange "d d d d d d" 0 $extrabrackets]
  361.     if {[eval regexp [list $searchString] [list $fileText] $dmy match fileText]} {
  362.         lappend matches $match
  363.     } else {
  364.         break
  365.     }
  366.     }
  367.     if {[info exists matches]} {
  368.     return $matches
  369.     } else {
  370.     return ""
  371.     }
  372. }
  373.  
  374. ## 
  375.  # -------------------------------------------------------------------------
  376.  #     
  377.  #    "file::getModeForFile" --
  378.  #    
  379.  #     This is an    adaptation of Tom Pollard's    emacs mode setting facility.
  380.  #     I call    it from    activateHook, which    means it takes effect before
  381.  #     the window    yet    exists,    so you don't get a double redraw.
  382.  #     Here are Tom's    comments from the original:
  383.  #       
  384.  #       # Emacs-style mode selection    using first    nonblank line of file
  385.  #       #
  386.  #       # Checks    for    interpreter    line "#!/dir/subdir/command    ...", or
  387.  #       # explicit major    mode election "-*-Mode:    vars ...-*-".
  388.  #       #
  389.  #       # "command" or "Mode" is    compared (case-insensitively) to Alpha mode
  390.  #       # names and first matching mode is used for the file.
  391.  #       #
  392.  #       # Author:   Tom Pollard    <pollard@chem.columbia.edu>
  393.  #       # Modified: 9/11/95
  394.  #    
  395.  #    Note: this proc actually opens the file for reading.  It _must_ close
  396.  #    the file before exiting.  If you modify this proc, make sure that
  397.  #    happens!
  398.  #    
  399.  #  To Do: I currently use 'file exists' to catch activation of non-file 
  400.  #           windows such as '*tcl shell*'. There may be a better way.
  401.  #
  402.  # --Version--Author------------------Changes-------------------------------  
  403.  #      1.0      <darley@fas.harvard.edu> first modification from Tom Pollard's
  404.  #    1.1     <darley@fas.harvard.edu> copes with a common Tcl/Tk exec trick.
  405.  #    1.2     <darley@fas.harvard.edu> can map creators if desired.
  406.  # -------------------------------------------------------------------------
  407.  ##
  408. if {[info tclversion] < 8.0} {
  409. proc file::getModeForFile {name} {
  410.     # if it doesn't exist as a file it's probably a funny window, so return
  411.     if {![file exists "$name"]} { 
  412.     if {[string first "* Trace" $name] == "0" } {
  413.         zoom
  414.         toggleScrollbar
  415.         return Tcl
  416.     }
  417.     return
  418.     }
  419.     global modeCreator
  420.     if {[info exists modeCreator([set sig [getFileSig $name]])]} {
  421.     return $modeCreator($sig)
  422.     }
  423.     if {[catch [list open "$name" r] fid]} { return }
  424.     # find first non-empty line. Return if we fail
  425.     for { set line "" } { [string trim $line] == "" } {} {
  426.     if { [gets $fid line] == -1} { close $fid ; return }
  427.     }
  428.     if {[regexp -nocase {^[^\n\r]*[-# \(]install($|[- \)])} $line]} {
  429.     global HOME
  430.     if {![string match [file join ${HOME} Tcl *] $name]} {
  431.         if {[catch {file readlink [file join ${HOME} Tcl]} link] || ![string match [file join $link *] $name]} {
  432.         close $fid
  433.         return "Inst"
  434.         }
  435.     }
  436.     }
  437.     if {[regexp {^#![     ]*([^     \n\r]+)} $line dmy mtch] } {
  438.     if {[regexp {([^/]+)$} $mtch majorMode]} { 
  439.         # remove trailing version number
  440.         set majorMode [string trimright $majorMode "01234567890."]
  441.         if {$majorMode == "sh"} {
  442.         # need to check if we're using a common unix trick
  443.         if {[gets $fid ll] != -1} {
  444.             while {[string index [string trimleft $ll] 0] == "#"} {
  445.             if {[gets $fid ll] == -1} { close $fid ; return }
  446.             } 
  447.         } else {
  448.             if {[regexp {[\n\r][ \t]*[^#][^\r\n]*[\r\n]} $line ll]} {
  449.             set ll [string trimleft $ll]
  450.             } else {
  451.             set ll ""
  452.             }
  453.         }
  454.         if {[regexp {^exec +([^ ]+) } $ll dummy ll]} {
  455.             regexp {([^/]+)$} [string trimright $ll "01234567890."] majorMode
  456.         }
  457.         }        
  458.     } else {
  459.         close $fid
  460.         return 
  461.     }
  462.     } elseif {[regexp {\-\*\- *(Mode:)? *([^     :;]+).*\-\*\-} $line "" "" majorMode]} {
  463.     # do nothing
  464.     } else {
  465.     close $fid
  466.     return
  467.     }
  468.     close $fid
  469.     
  470.     global unixMode
  471.     set majorMode [string tolower $majorMode]
  472.     if {[info exists unixMode($majorMode)]} {
  473.     return $unixMode($majorMode)
  474.     } else {
  475.     global mode::features
  476.     set m [array names mode::features]
  477.     if {[set i [lsearch [string tolower $m] $majorMode]] != -1} {
  478.         return [lindex $m $i]
  479.     }
  480.     }
  481.     return 
  482. }
  483. } else {
  484.     proc file::getModeForFile {name} {
  485.     # if it doesn't exist as a file it's probably a funny window, so return
  486.     if {![file exists "$name"]} { 
  487.         if {[string first "* Trace" $name] == "0" } {
  488.         zoom
  489.         toggleScrollbar
  490.         return Tcl
  491.         }
  492.         return
  493.     }
  494.     global modeCreator
  495.     if {[info exists modeCreator([set sig [getFileSig $name]])]} {
  496.         return $modeCreator($sig)
  497.     }
  498.     if {[catch [list ::open "$name" r] fid]} { return }
  499.     # find first non-empty line. Return if we fail
  500.     for { set line "" } { [string trim $line] == "" } {} {
  501.         if { [gets $fid line] == -1} { ::close $fid ; return }
  502.     }
  503.     if {[regexp -nocase {^[^\n\r]*[-# \(]install($|[- \)])} $line]} {
  504.         global HOME
  505.         if {![string match [file join ${HOME} Tcl *] $name]} {
  506.         if {[catch {file readlink [file join ${HOME} Tcl]} link] || ![string match [file join $link *] $name]} {
  507.             ::close $fid
  508.             return "Inst"
  509.         }
  510.         }
  511.     }
  512.     if {[regexp {^#![    ]*([^    \n\r]+)} $line dmy mtch] } {
  513.         if {[regexp {([^/]+)$} $mtch majorMode]} { 
  514.         # remove trailing version number
  515.         set majorMode [string trimright $majorMode "01234567890."]
  516.         if {$majorMode == "sh"} {
  517.             # need to check if we're using a common unix trick
  518.             if {[gets $fid ll] != -1} {
  519.             while {[string index [string trimleft $ll] 0] == "#"} {
  520.                 if {[gets $fid ll] == -1} { ::close $fid ; return }
  521.             } 
  522.             } else {
  523.             if {[regexp {[\n\r][ \t]*[^#][^\r\n]*[\r\n]} $line ll]} {
  524.                 set ll [string trimleft $ll]
  525.             } else {
  526.                 set ll ""
  527.             }
  528.             }
  529.             if {[regexp {^exec +([^ ]+) } $ll dummy ll]} {
  530.             regexp {([^/]+)$} [string trimright $ll "01234567890."] majorMode
  531.             }
  532.         }        
  533.         } else {
  534.         ::close $fid
  535.         return 
  536.         }
  537.     } elseif {[regexp {\-\*\- *(Mode:)? *([^    :;]+).*\-\*\-} $line "" "" majorMode]} {
  538.         # do nothing
  539.     } else {
  540.         ::close $fid
  541.         return
  542.     }
  543.     ::close $fid
  544.     
  545.     global unixMode
  546.     set majorMode [string tolower $majorMode]
  547.     if {[info exists unixMode($majorMode)]} {
  548.         return $unixMode($majorMode)
  549.     } else {
  550.         global mode::features
  551.         set m [array names mode::features]
  552.         if {[set i [lsearch [string tolower $m] $majorMode]] != -1} {
  553.         return [lindex $m $i]
  554.         }
  555.     }
  556.     return 
  557.     }
  558. }
  559.     
  560. # These are mappings required by the above proc.  If you need to extend this 
  561. # list to include a mode you are writting, place a statement like the following 
  562. # in your alpha::mode body
  563. set unixMode(matlab) {MATL}
  564.  
  565. ## 
  566.  # -------------------------------------------------------------------------
  567.  # 
  568.  # "file::whichModeForWin" --
  569.  # 
  570.  #  Copes with trailing '<2>', .orig, copy, '~',...
  571.  # -------------------------------------------------------------------------
  572.  ##
  573. proc file::whichModeForWin {name} {
  574.     regexp {(.*) <[0-9]+>$} $name dmy name
  575.     if {[set m [file::getModeForFile $name]] != ""} { return $m }
  576.     global ModeSuffixes
  577.     set nm [file tail $name]    
  578.     regsub {( copy|~[0-9]*|.orig)+$} $nm "" nm
  579.     case $nm in $ModeSuffixes
  580.     return $winMode
  581. }
  582.  
  583. # Below:
  584. #        Expanded version of old 'DblClickAux.tcl'
  585. # Authors: Tom Pollard <pollard@chem.columbia.edu>
  586. #      Tom Scavo   <trscavo@syr.edu>
  587. #      Vince Darley <darley@fas.harvard.edu>
  588. #  modified by  rev reason
  589. #  -------- --- --- -----------
  590. #  9/97     VMD 1.0 reorganised for new alpha distribution.
  591. # ###################################################################
  592. ##
  593.  
  594. #############################################################################
  595. # Take any valid Macintosh filespec as input, and return the
  596. # corresponding absolute filespec.  Filenames without an explicit
  597. # folder are resolved relative to the folder of the current document.
  598. #
  599. proc file::absolutePath {filename} {
  600.     set    name [file tail    $filename]
  601.     set    subdir [file dirname $filename]
  602.     if { [string length $subdir] > 0 && [string index $subdir 0] != ":" } {
  603.     set dir ""
  604.     } else {
  605.     set dir [file dirname [lindex [winNames    -f] 0]]
  606.     }
  607.     return [file join $dir$subdir $name]
  608. }
  609.  
  610. #############################################################################
  611. # Open the file specified by the full pathname "$filename"
  612. # If it's already open, just switch to it without any fuss.
  613. #
  614. proc file::openQuietly {filename} {
  615.     edit -c -w $filename
  616. }
  617.  
  618. if {[info tclversion] < 8.0} {
  619. #############################################################################
  620. # Searches $filename for the given pattern $searchString.  If the 
  621. # search is successful, returns the matched string; otherwise returns
  622. # the empty string.  If the flag 'indices' is true and the search is
  623. # successful, returns a list of two pos giving the indices of the
  624. # found string; otherwise returns the list '-1 -1'.
  625. #
  626. proc file::searchFor {filename searchString {indices 0}} {
  627.     # Get the text of the file to be searched:
  628.     if {[lsearch [winNames -f] $filename] >= 0} {
  629.     set fileText [getText -w $filename [minPos] [maxPos -w $filename]]
  630.     } elseif {[file exists $filename]} {
  631.     set fd [open $filename]
  632.     set fileText [read $fd]
  633.     close $fd
  634.     } else {
  635.     if { $indices } {
  636.         return [list -1 -1]
  637.     } else {
  638.         return ""
  639.     }
  640.     }
  641.     # Search the text for the search string:
  642.     if { $indices } {
  643.     if {[regexp -indices $searchString $fileText mtch]} {
  644.         # Fixes an apparent bug in 'regexp':
  645.         return [list [lindex $mtch 0] [expr {[lindex $mtch 1] + 1}]]
  646.     } else {        
  647.         return [list -1 -1]
  648.     }
  649.     } else {
  650.     if {[regexp $searchString $fileText mtch]} {
  651.         return $mtch
  652.     } else {        
  653.         return ""
  654.     }
  655.     }
  656. }
  657.  
  658. #############################################################################
  659. #  Read and return the complete contents of the specified file.
  660. #
  661. proc file::readAll {fileName} {
  662.     if {[file exists $fileName] && [file readable $fileName]} {
  663.     set fileid [open $fileName "r"]
  664.     set contents [read $fileid]
  665.     close $fileid
  666.     return $contents
  667.     } else {
  668.     error "No readable file found"
  669.     }
  670. }
  671.  
  672.  
  673. #############################################################################
  674. #  Save $text in $filename.  If $text is null, create an empty file.
  675. #  Overwrite if {$overwrite} is true or the file does not exist; 
  676. #  otherwise, prompt the user.
  677. #
  678. proc file::writeAll {filename {text {}} {overwrite 0}} {
  679.     if { $overwrite || ![file exists $filename] } {
  680.     message "Saving $filename…"
  681.     set fd [open $filename "w"]
  682.     puts $fd $text
  683.     close $fd
  684.     } else {
  685.     if {[dialog::yesno "File $filename exists!  Overwrite?"]} {
  686.         file::writeAll $filename $text 1
  687.     } else {
  688.         message "No file written"
  689.     }
  690.     }
  691. }
  692. } else {
  693.     #############################################################################
  694.     # Searches $filename for the given pattern $searchString.  If the 
  695.     # search is successful, returns the matched string; otherwise returns
  696.     # the empty string.  If the flag 'indices' is true and the search is
  697.     # successful, returns a list of two pos giving the indices of the
  698.     # found string; otherwise returns the list '-1 -1'.
  699.     #
  700.     proc file::searchFor {filename searchString {indices 0}} {
  701.     # Get the text of the file to be searched:
  702.     if {[lsearch [winNames -f] $filename] >= 0} {
  703.         set fileText [getText -w $filename [minPos] [maxPos -w $filename]]
  704.     } elseif {[file exists $filename]} {
  705.         set fd [::open $filename]
  706.         set fileText [::read $fd]
  707.         ::close $fd
  708.     } else {
  709.         if { $indices } {
  710.         return [list -1 -1]
  711.         } else {
  712.         return ""
  713.         }
  714.     }
  715.     # Search the text for the search string:
  716.     if { $indices } {
  717.         if {[regexp -indices $searchString $fileText mtch]} {
  718.         # Fixes an apparent bug in 'regexp':
  719.         return [list [pos::math [minPos] + [lindex $mtch 0]] \
  720.           [pos::math [minPos] + [expr {[lindex $mtch 1] + 1}]]]
  721.         } else {        
  722.         return [list -1 -1]
  723.         }
  724.     } else {
  725.         if {[regexp $searchString $fileText mtch]} {
  726.         return $mtch
  727.         } else {        
  728.         return ""
  729.         }
  730.     }
  731.     }
  732.     
  733. #############################################################################
  734.     #  Read and return the complete contents of the specified file.
  735.     #
  736.     proc file::readAll {fileName} {
  737.     if {[file exists $fileName] && [file readable $fileName]} {
  738.         set fileid [::open $fileName "r"]
  739.         set contents [::read $fileid]
  740.         ::close $fileid
  741.         return $contents
  742.     } else {
  743.         error "No readable file found"
  744.     }
  745.     }
  746.     
  747.  
  748.     #############################################################################
  749.     #  Save $text in $filename.  If $text is null, create an empty file.
  750.     #  Overwrite if {$overwrite} is true or the file does not exist; 
  751.     #  otherwise, prompt the user.
  752.     #
  753.     proc file::writeAll {filename {text {}} {overwrite 0}} {
  754.     if { $overwrite || ![file exists $filename] } {
  755.         message "Saving $filename…"
  756.         set fd [::open $filename "w"]
  757.         puts $fd $text
  758.         ::close $fd
  759.     } else {
  760.         if {[dialog::yesno "File $filename exists!  Overwrite?"]} {
  761.         file::writeAll $filename $text 1
  762.         } else {
  763.         message "No file written"
  764.         }
  765.     }
  766.     }
  767. }
  768.     
  769.  
  770. #############################################################################
  771. #  Highlight (select) a particular line in the designated file, opening the
  772. #  file if necessary.  Returns the full name of the buffer containing the
  773. #  opened file.  If provided, a message is displayed on the status line.
  774. #
  775. proc file::gotoLine {fname line {mesg {}}} {
  776.     if {[lsearch [winNames -f] "*$fname"] >= 0} {
  777.     bringToFront $fname
  778.     } elseif {[lsearch [winNames] "*$fname"] >= 0} {
  779.     bringToFront $fname
  780.     } elseif {[file exists $fname]} {
  781.     edit $fname
  782.     catch {shrinkWindow 2}
  783.     } else {
  784.     alertnote "File \" $fname \" not found."
  785.     return
  786.     }
  787.     set pos [rowColToPos $line 0]
  788.     select [lineStart $pos] [nextLineStart $pos]
  789.     if {[string length $mesg]} { message $mesg }
  790.     return [win::Current]
  791. }
  792.  
  793. #############################################################################
  794. # Return a list of all subfolders found within $folder,
  795. # down to some maximum recursion depth.  The top-level
  796. # folder is not included in the returned list.
  797. #
  798. proc file::hierarchy {folder {depth 3}} {
  799.     set folders {}
  800.     if {$depth > 0} {
  801.     global file::separator
  802.     incr depth -1
  803.     if {[string length [file tail $folder]] > 0} {
  804.         set folder "$folder${file::separator}"
  805.     }
  806.     foreach m [glob -nocomplain  $folder\*] {
  807.         if {[file isdirectory $m]} {
  808.         set folders [concat $folders [list $m]]
  809.         set folders [concat $folders [file::hierarchy ${m}${file::separator} $depth]]
  810.         }
  811.     }
  812.     }
  813.     return $folders
  814. }
  815.  
  816. proc file::touch {f {depth 3}} {
  817.     if {[file isfile $f]} {
  818.     setFileInfo $f modified [now]
  819.     return
  820.     }
  821.     if {$depth == 0} {return}
  822.     foreach ff [glob [file join $f *]] {
  823.     file::touch $ff [expr {$depth -1}]
  824.     }
  825. }
  826.  
  827. proc file::revertThese {args} {
  828.     foreach f $args {
  829.     foreach w [winNames -f] {
  830.         set ww $w
  831.         regsub { <[0-9]+>$} $w {} w
  832.         if {$f == $w} {
  833.         bringToFront $ww
  834.         revert
  835.         }
  836.     }
  837.     }
  838. }
  839.  
  840. ## 
  841.  # -------------------------------------------------------------------------
  842.  # 
  843.  # "file::completeFromDir" --
  844.  # 
  845.  #  Here's a good example:
  846.  # 
  847.  #    set filename [prompt::statusLineComplete "Open which header" \
  848.  #       [list file::completeFromDir $universalHeadersFolder] \
  849.  #        -nocache -tryuppercase]
  850.  #  
  851.  #  Returns the list of files in '$dir' which start with '$f'.
  852.  # -------------------------------------------------------------------------
  853.  ##
  854. proc file::completeFromDir {dir f} {
  855.     set old [pwd]
  856.     cd $dir
  857.     set res [glob -nocomplain ${f}*]
  858.     cd $old
  859.     return $res
  860. }
  861.  
  862.  
  863.  
  864.